package wangwenjun.phase1.ticketwindow;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import util.ThreadUtil;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.stream.IntStream;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

/**
 * @author ChangLiang
 * @date 2020/5/29
 */
@Slf4j
public class AppTest {

    @Test
    @DisplayName("testV1")
    public void test() throws InterruptedException {
        final TicketWindowV1Runnable task = new TicketWindowV1Runnable();
        doConcurrentExecute(task);
        Thread.currentThread().join();

        // output: 有重复数字

    }

    @Test
    @DisplayName("testV2--static")
    public void test2() throws InterruptedException {
        final TicketWindowV2Runnable task = new TicketWindowV2Runnable();
        doConcurrentExecute(task);
        Thread.currentThread().join();

        // output:
        /*[2020-05-29 14:36:45:759] [四号窗口] [INFO ] -- Thread[四号窗口,5,main]的号码是：39
        [2020-05-29 14:36:45:759] [二号窗口] [INFO ] -- Thread[二号窗口,5,main]的号码是：39
        [2020-05-29 14:36:46:061] [二号窗口] [INFO ] -- Thread[二号窗口,5,main]的号码是：51*/
    }

    @Test
    @DisplayName("testV3--volatile")
    public void test3() throws InterruptedException {

        TicketWindowV3Runnable task = new TicketWindowV3Runnable();

        doConcurrentExecute(task);
        System.out.println(task.getSetSize());
        System.out.println(task.getListSize());
    }

    @Test
    @DisplayName("testV4--synchronized")
    public void test4() throws InterruptedException {

        TicketWindowV4Runnable task = new TicketWindowV4Runnable();

        // 这种情况下仍然是可能出现51的情况的
        doConcurrentExecute(task);
        System.out.println(task.getSetSize());
        System.out.println(task.getListSize());
    }

    @RepeatedTest(value = 5)
    @DisplayName("testV5--synchronized")
    public void test5() throws InterruptedException {

        TicketWindowV5Runnable task = new TicketWindowV5Runnable();

        doConcurrentExecute(task);
        assertThat(task.getSetSize()).isEqualTo(TicketWindowV5Runnable.MAX);
        assertThat(task.getListSize()).isEqualTo(TicketWindowV5Runnable.MAX);
    }

    @RepeatedTest(value = 5)
    @DisplayName("testV6--AtomicInteger")
    public void test6() throws InterruptedException {

        TicketWindowV6Runnable task = new TicketWindowV6Runnable();

        doConcurrentExecute(task);
        assertThat(task.getSetSize()).isEqualTo(TicketWindowV5Runnable.MAX);
        assertThat(task.getListSize()).isEqualTo(TicketWindowV5Runnable.MAX);
    }

    private void doConcurrentExecute(Runnable task) throws InterruptedException {
        Thread windowThread1 = new Thread(task, "一号窗口");
        Thread windowThread2 = new Thread(task, "二号窗口");
        Thread windowThread3 = new Thread(task, "三号窗口");
        Thread windowThread4 = new Thread(task, "四号窗口");
        windowThread1.start();
        windowThread2.start();
        windowThread3.start();
        windowThread4.start();

        windowThread1.join();
        windowThread2.join();
        windowThread3.join();
        windowThread4.join();
    }

    @Test
    @DisplayName("test v7")
    public void test7() throws InterruptedException {

        /**
         * 这段程序既没有对共享资源进行共享锁的保护，也没有进行不可变的设计
         * 其可能发生
         * 1 跳过某个数字
         * 2 重复某个数字
         * 3 超过50
         */

        int delta = 1;
        TicketWindowV7 ticketWindowV7 = new TicketWindowV7(0);
        IntStream.rangeClosed(1, 4).forEach(i -> new Thread(() -> {
            while (true) {
                int oldValue = ticketWindowV7.getValue();
                int result = ticketWindowV7.add(delta);
                if (result > 50) {
                    break;
                }
                log.info("Thread:{} print:{}", Thread.currentThread(), result);
                if (delta + oldValue != result) {
                    log.error("ERROR:{}+{}={}", oldValue, delta, result);
                }
                ThreadUtil.sleepForMilliSeconds(100);
            }
        }, "No." + i + " window").start());

        Thread.currentThread().join();
    }

    @Test
    @DisplayName("test v7 - synchronized")
    public void tes8() throws InterruptedException {

        int delta = 1;
        TicketWindowV7 ticketWindowV7 = new TicketWindowV7(0);
        IntStream.rangeClosed(1, 4).forEach(i -> new Thread(() -> {

            while (true) {
                int oldValue;
                int result;
                /**
                 * 这里将数据同步的控制放在了线程的逻辑执行单元中
                 * 在TicketWindowV7中未增加任何同步的控制
                 * 如果单纯对getValue 方法和add 方法增加同步控制，虽然保证了单个方法的原子性，但是两个原子类型的操作在一起未必就是原子性的，
                 * 因此在线程的逻辑执行单元中增加同步控制是最为合理的。
                 */
                synchronized (TicketWindowV7.class) {
                    oldValue = ticketWindowV7.getValue();
                    result = ticketWindowV7.add(delta);
                    if (result > 50) {
                        break;
                    }
                }
                log.info("Thread:{} print:{}", Thread.currentThread(), result);
                if (delta + oldValue != result) {
                    log.error("ERROR:{}+{}={}", oldValue, delta, result);
                }
                ThreadUtil.sleepForMilliSeconds(100);
            }
        }, "No." + i + " window").start());

        Thread.currentThread().join();
    }

    @Test
    @DisplayName("test v8")
    public void test9() throws InterruptedException {
        TicketWindowV8 ticketWindowV8 = new TicketWindowV8(0);
        IntStream.rangeClosed(1, 4).forEach(i -> new Thread(() -> {
            int delta = i;
            while (true) {
                int oldValue = ticketWindowV8.getValue();
                int result = ticketWindowV8.add(delta).getValue();
                if (result > 50) {
                    break;
                }
                log.info("Thread:{} print:{}", Thread.currentThread(), result);
                if (delta + oldValue != result) {
                    log.error("ERROR:{}+{}={}", oldValue, delta, result);
                }
                delta = delta + 4;
                ThreadUtil.sleepForMilliSeconds(100);
            }
        }, "No." + i + " window").start());

        Thread.currentThread().join();

        // output:
        /*Thread:Thread[No.1 window,5,main] print:1,5,9...49  13个No

        Thread:Thread[No.2 window,5,main] print:2,6,10...50  13个No

        Thread:Thread[No.3 window,5,main] print:3,7,11...47  12个No

        Thread:Thread[No.4 window,5,main] print:4,8,12...48  12个No*/
    }

    @Test
    @DisplayName("test AtomicIntegerFieldUpdater")
    public void test10() throws InterruptedException {
        final AtomicIntegerFieldUpdater<TicketNo> updater = AtomicIntegerFieldUpdater.newUpdater(TicketNo.class, "no");
        final TicketNo ticketNo = new TicketNo();
        IntStream.rangeClosed(1, 4).forEach(i -> new Thread(() -> {
            while (true) {
                int result = updater.getAndIncrement(ticketNo);
                if (result > 50) {
                    break;
                }
                log.info("Thread:{} print:{}", Thread.currentThread(), result);
                ThreadUtil.sleepForMilliSeconds(100);
            }
        }, "No." + i + " window").start());

        Thread.currentThread().join();
    }

    static class TicketNo{
        volatile int no = 1;
    }
}
